
#include <stdint.h>
#include <stdlib.h>
#include "os.h"
#include "appload.h"
#include "sysdef.h"
#include "debug.h"

#define appload_dbg(fmt, ...) custom_log("appload", fmt, ##__VA_ARGS__)

extern appRegItem_t __appRegTable_start[];
extern appRegItem_t __appRegTable_end[];

typedef struct 
{
    OS_TCB * p_tcb;
    CPU_STK * p_stk_base;
    app_entry_t user_entry;
    char * task_name;
    void * user_args;
} task_info_t;

static OS_Q app_exit_q;
static OS_TCB app_exit_hdl_task;
static CPU_STK app_exit_hdl_task_stack[512];

static void appRegTableSplitArray(appRegItem_t* arr, int low, int high, int *p ) 
{ 
    int i, j;
    int item;
    appRegItem_t t;

    item = arr[low].startup_sequence;
    i = low;
    j = high;

    while( i < j )
    {
        /*  move from R to L in search of element < item */
        while ( arr[j].startup_sequence> item )
            j = j - 1;

        /*  move from L to R in search of element > item */
        while ( arr[i].startup_sequence<= item  &&  i < j )
            i = i + 1;

        if ( i < j )
        {
            t = arr[i];
            arr[i] = arr[j];
            arr[j] = t;
        }
    }
    *p = j;
    t = arr[low];
    arr[low] = arr[*p];
    arr[*p] = t;
}

static void appRegTableQuickSort(appRegItem_t* arr, int low, int high )
{
    int pos;

    if( low < high )
    {
        appRegTableSplitArray( arr, low, high, &pos );
        appRegTableQuickSort( arr, low, pos - 1 );
        appRegTableQuickSort( arr, pos + 1, high );
    }
}

static void AppStartupTask(void *argv)
{
    OS_ERR err;
    task_info_t * task_info = (task_info_t *)argv;
    
    appload_dbg("app(%p, %s) started", task_info->user_entry, task_info->task_name);
    
    (task_info->user_entry)(task_info->user_args);
    OSQPost(&app_exit_q, task_info, sizeof(task_info_t), OS_OPT_POST_FIFO, &err);
    OSTaskSuspend(NULL, &err);
}

static void AppExitHandleTask(void *argv)
{
    UNUSED(argv);

    appload_dbg("AppExitHandleTask startup ...");
    
    while(1)
    {
        OS_ERR err;
        OS_MSG_SIZE msg_size = 0;
        task_info_t * task_info = NULL;

        task_info = OSQPend(&app_exit_q, OS_WAIT_FOREVER, OS_OPT_PEND_BLOCKING, &msg_size, NULL, &err);
        if(task_info)
        {
            OSTaskDel(task_info->p_tcb, &err);
            free(task_info->p_tcb);
            free(task_info->p_stk_base);
            free(task_info);
        }
    }
}

void app_load(void)
{
    OS_ERR err;
    uint32_t appRegItemNum = __appRegTable_end - __appRegTable_start;
    appRegItem_t * appRegTable = __appRegTable_start;
    uint32_t itemIdx = 0;

    appload_dbg("appRegTable start addr: %p", __appRegTable_start);
    appload_dbg("appRegTable end addr:   %p", __appRegTable_end);
    appload_dbg("size of per appRegItem: 0x%02x", sizeof(appRegItem_t));
    appload_dbg("appRegItemNum: %lu", appRegItemNum);

    CPU_SR_ALLOC();
    OS_CRITICAL_ENTER();

    OSQCreate(&app_exit_q, "appexit_q", 16, &err);
    OSTaskCreate(&app_exit_hdl_task,
        "appexit_hdl",
        AppExitHandleTask,
        NULL,
        10,
        app_exit_hdl_task_stack,
        ARR_SIZE(app_exit_hdl_task_stack) / 10,
        ARR_SIZE(app_exit_hdl_task_stack),
        0,
        3,
        NULL,
        OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR,
        &err);

    if(appRegItemNum)
        appRegTableQuickSort(appRegTable, 0, appRegItemNum - 1);

    for(; itemIdx < appRegItemNum; itemIdx++)
    {
        appload_dbg("app[%lu] (%p, %s) starting ...", itemIdx, appRegTable[itemIdx].entry, appRegTable[itemIdx].name);

        task_info_t * task_info = malloc(sizeof(task_info_t));
        ASSERT(task_info != NULL);

        task_info->p_tcb = malloc(sizeof(OS_TCB));
        ASSERT(task_info->p_tcb);

        CPU_STK_SIZE stk_size = (appRegTable[itemIdx].stack_size + 3) / 4;
        task_info->p_stk_base = malloc(stk_size * 4);
        ASSERT(task_info->p_stk_base);

        task_info->user_entry = appRegTable[itemIdx].entry;
        task_info->task_name = appRegTable[itemIdx].name;
        task_info->user_args = appRegTable[itemIdx].argv;
        
        OSTaskCreate(task_info->p_tcb,
            appRegTable[itemIdx].name,
            AppStartupTask,
            task_info,
            appRegTable[itemIdx].task_prio,
            task_info->p_stk_base,
            stk_size / 10,
            stk_size,
            0,
            3,
            NULL,
            OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR,
            &err);
    }

    OS_CRITICAL_EXIT();

    OSStart(&err);
}
